#include "transfile.h"

// 完整发送
int sendn(int fd, const void *buf, size_t n) {
    size_t left = n; // 剩余需要发送的字节数
    const char *p = (const char *)buf; // 指向待发送数据的指针
    while(left > 0) { 
        ssize_t sent = send(fd, p, left, 0); // 尝试发送剩余数据
        if(sent <= 0) // 发送出错或者连接关闭
        {
            return -1;
        } 
        left -= sent; // 更新剩余的字节数
        p += sent; // 移动指针到未发送的数据位置
    }
    return 0; // 走到这说明成功发送所有数据
}

// 完整接收
int recvn(int fd, void *buf, size_t n) {
    size_t left = n; // 剩余需要接收的字节数
    char *p = (char *)buf; // 指向接收缓冲区的指针
    while(left > 0) {
        ssize_t recvd = recv(fd, p, left, 0); // 尝试接收剩余数据
        if(recvd <= 0)  // 接收出错或者连接关闭
        {
            return -1; 
        }
        left -= recvd; // 更新剩余的字节数
        p += recvd; // 移动指针到缓冲区未填充的位置
    } 
    return 0; // 走到这说明成功接收所有数据
}


// 发送TLV数据包
int send_tlv(int fd, int type, const void *data, size_t length) {
    tlv_t header;
    header.type = htonl(type);
    header.length = htonl(length);
    
    if(sendn(fd, &header, sizeof(header))) {
        return -1;
    }
    
    if(length > 0 && data != NULL) {
        if(sendn(fd, data, length)) {
            return -1;
        }
    }
    
    return 0;
}

// 接收TLV数据包
int recv_tlv(int fd, int *type, void *buf, size_t *length) {
    tlv_t header;
    
    if(recvn(fd, &header, sizeof(header))) {
        return -1;
    }
    
    *type = ntohl(header.type);
    size_t data_len = ntohl(header.length);
    
    if(buf != NULL && *length < data_len) {
        return -1;
    }
    
    if(data_len > 0) {
        if(recvn(fd, buf, data_len)) {
            return -1;
        }
    }
    
    *length = data_len;
    return 0;
}

// 发送文件
int send_file(int netfd, const char *filename, int resume) {
    struct stat statbuf; // 使用stat系统调用函数获取文件信息
    if(stat(filename, &statbuf)) {
        perror("stat");
        return -1;
    }

    // 断点续传处理
    off_t offset = 0;
    if (resume) {
        // 接收偏移量TLV
        size_t len = sizeof(offset);
        int type;
        if(recv_tlv(netfd, &type, &offset, &len)) {
            perror("recv resume offset");
            return -1;
        }
        // 校验偏移量不超过文件大小
        if (offset > statbuf.st_size) {
            offset = statbuf.st_size;
        }
    }

    // 发送文件大小TLV  将文件大小转换为网络字节序后发给客户端
    off_t net_filesize = htobe64(statbuf.st_size);
    if(send_tlv(netfd, TYPE_FILESIZE, &net_filesize, sizeof(net_filesize))) {
        perror("send filesize");
        return -1;
    }

    // 打开文件
    int fd = open(filename, O_RDONLY);
    if(fd == -1) {
        perror("open");
        return -1;
    }
    
    // 将文件描述符指针定位到断点位置
    if (offset > 0 && lseek(fd, offset, SEEK_SET) == -1) {
        perror("lseek");
        close(fd);
        return -1;
    }
    
    // 发送文件数据
    off_t total_sent = offset;
    char buf[4096];
    while(total_sent < statbuf.st_size) {
        ssize_t ret = read(fd, buf, sizeof(buf));
        if(ret <= 0) break;
        
        // 发送文件数据TLV
        if(send_tlv(netfd, TYPE_FILEDATA, buf, ret)) {
            perror("send file data");
            break;
        }
        
        total_sent += ret;
    }
    close(fd);

    if(total_sent != statbuf.st_size) {
        fprintf(stderr, "Send incomplete: %ld/%ld bytes\n", 
                (long)total_sent, (long)statbuf.st_size);
        return -1;
    }

    return 0;
}

// 接收文件
int recv_file(int netfd, const char *filename, int resume) {
    off_t filesize = 0;
    off_t offset = 0;
       
    // 断点续传处理
    if (resume) {
        // 接收文件大小TLV
        size_t len = sizeof(filesize);
        int type;
        if(recv_tlv(netfd, &type, &filesize, &len)) {
            perror("recv filesize for resume");
            return -1;
        }

        // 检查本地文件
        struct stat statbuf;
        if (stat(filename, &statbuf) == 0) {
            offset = statbuf.st_size;
        }
        // 发送偏移量TLV
        if(send_tlv(netfd, TYPE_OFFSET, &offset, sizeof(offset))) {
            perror("send resume offset");
            return -1;
        }
        // 检查是否已经发完
        if (offset >= filesize) {
            printf("File already complete: %s\n", filename);
            return 0;
        }

    } else {
        // 普通模式接收文件大小TLV
        off_t net_filesize;
        size_t len = sizeof(net_filesize);
        int type;
        if(recv_tlv(netfd, &type, &net_filesize, &len)) {
            perror("recv filesize");
            return -1;
        }
        filesize = be64toh(net_filesize);
    }

    // 打开文件
    int flags = O_WRONLY | O_CREAT;
    if (resume && offset > 0) {
        flags |= O_APPEND;
    } else {
        flags |= O_TRUNC;
    }
    
    int fd = open(filename, flags, 0666);
    if(fd == -1) {
        perror("open");
        return -1;
    }
    
    // 接收文件数据
    off_t total_recv = offset;
    char buf[4096];
    while(total_recv < filesize) {
        size_t len = sizeof(buf);
        int type;
        if(recv_tlv(netfd, &type, buf, &len)) {
            perror("recv file data");
            break;
        }
        
        if(write(fd, buf, len) != (ssize_t)len) {
            perror("write");
            break;
        }
        total_recv += len;
    }
    close(fd);

    if(total_recv != filesize) {
        fprintf(stderr, "Recv incomplete: %ld/%ld bytes\n", 
                (long)total_recv, (long)filesize);
        return -1;
    }

    return 0;
}
/*
int handle_command(int netfd,char *filename,int cmd){
	// 客户端要上传文件时，先检查本地文件
    if(cmd == CMD_UPLOAD){
        off_t offset = 0;    
        struct stat statbuf;
        if (stat(filename, &statbuf) == 0) {
            offset = statbuf.st_size;
        }
        if((cmd == CMD_UPLOAD) && (offset > 0)) {
            cmd = CMD_UPLOAD_RESUME;
        }
        // 发送偏移量TLV
        if(send_tlv(netfd, TYPE_OFFSET, &offset, sizeof(offset))) {
            perror("send resume offset");
            return -1;
        }
    }
        
    // 根据命令处理
    int status = 0;
    switch(cmd) {
        case CMD_UPLOAD:
            printf("Receiving file: %s\n", filename);
            status = recv_file(netfd, filename, 0);
            break;

        case CMD_DOWNLOAD:
            printf("Sending file: %s\n", filename);
            status = send_file(netfd, filename, 0);
            break;
            
        case CMD_UPLOAD_RESUME:
            printf("Resuming receive of file: %s\n", filename);
            status = recv_file(netfd, filename, 1);
            break;
            
        case CMD_DOWNLOAD_RESUME:
            printf("Resuming send of file: %s\n", filename);
            status = send_file(netfd, filename, 1);
            break;
            
        case CMD_DELETE: {
            printf("Deleting file: %s\n", filename);
            if(unlink(filename)) {
                perror("unlink");
                status = -1;
            }
            // 发送状态TLV
            send_tlv(netfd, TYPE_STATUS, &status, sizeof(status));
            break;
        }
            
        default:
            fprintf(stderr, "Unknown command: %d\n", cmd);
            status = -1;
    }

    return status;
}
*/

// 处理客户端发来的命令
int handle_command(int netfd) {
    // 接收命令TLV
    int cmd;
    size_t len = sizeof(cmd);
    int type;
    if(recv_tlv(netfd, &type, &cmd, &len)) {
        perror("recv command");
        return -1;
    }

    // 接收文件名TLV
    char filename[1024];
    len = sizeof(filename);
    if(recv_tlv(netfd, &type, filename, &len)) {
        perror("recv filename");
        return -1;
    }
    filename[len] = '\0';
    

    // 客户端要上传文件时，先检查本地文件
    if(cmd == CMD_UPLOAD){
        off_t offset = 0;    
        struct stat statbuf;
        if (stat(filename, &statbuf) == 0) {
            offset = statbuf.st_size;
        }
        if((cmd == CMD_UPLOAD) && (offset > 0)) {
            cmd = CMD_UPLOAD_RESUME;
        }
        // 发送偏移量TLV
        if(send_tlv(netfd, TYPE_OFFSET, &offset, sizeof(offset))) {
            perror("send resume offset");
            return -1;
        }
    }
        
    // 根据命令处理
    int status = 0;
    switch(cmd) {
        case CMD_UPLOAD:
            printf("Receiving file: %s\n", filename);
            status = recv_file(netfd, filename, 0);
            break;

        case CMD_DOWNLOAD:
            printf("Sending file: %s\n", filename);
            status = send_file(netfd, filename, 0);
            break;
            
        case CMD_UPLOAD_RESUME:
            printf("Resuming receive of file: %s\n", filename);
            status = recv_file(netfd, filename, 1);
            break;
            
        case CMD_DOWNLOAD_RESUME:
            printf("Resuming send of file: %s\n", filename);
            status = send_file(netfd, filename, 1);
            break;
            
        case CMD_DELETE: {
            printf("Deleting file: %s\n", filename);
            if(unlink(filename)) {
                perror("unlink");
                status = -1;
            }
            // 发送状态TLV
            send_tlv(netfd, TYPE_STATUS, &status, sizeof(status));
            break;
        }
            
        default:
            fprintf(stderr, "Unknown command: %d\n", cmd);
            status = -1;
    }

    return status;
}

